#
# This package handles loading, saving, listing matrices
#
# 2009 Mike Widner <mikewidner@gmail.com>
#
###########

package Fangcheng::DB;

use 5.006001;
use strict;
use warnings;
use vars qw(@ISA);
use Carp;
use Fangcheng::Matrix;

require Fangcheng;

@ISA = qw( Fangcheng );
our $VERSION = '0.01';

sub new {
	my ($class, $dbFile) = @_;
	# our list of matrices will be a hash of arrays to allow for some named keys
	my $self = {};
	bless $self, ref($class) || $class;
	
	if (defined $dbFile) {
		$self->load($dbFile);
	}
	
	return($self);
}

sub load {
	my ($self, $dbFile) = @_;
	unless (defined $dbFile) {
		croak "Must have file to load";
	}
	$self->dbFile( $dbFile );
	$self->matrices( $self->_readMatrixDB($dbFile) );		# only call internally
}

# opens file listing matrices, reads in entries, adds to hash, returns it
# these loaded matrices are keyed by number, not name
sub _readMatrixDB {
	my ($self, $dbFile) = @_;
	open(DB, "<$dbFile") or croak "Cannot open $dbFile for reading";
	my (%matrices, $total);
	my $index = 1;
	while (<DB>) {
		next if /^#|^$/;	# skip comments and blank lines
		if (/^(\[(\[(\-?\d+(\/\d+)?,?\s*)+\s*\],?\s*)+\])/) {
			$matrices{$index} = $self->_convertStringToMatrix($_);
			$index++;
		}
		if (/^MATRIX_TOTAL=(\d+)/) {
			$total = $1;
		}
	}
	close(DB);
	if ($total != $index - 1) {	# we're counting from 1
		croak "Wrong number of matrices found. Expected $total, Got $index";
	}
	
	if (not defined $total) {
		croak "MATRIX_TOTAL not defined";
	}
	
	return(\%matrices);
}

# take the matrix in string format and convert into FangchengMatrix object
sub _convertStringToMatrix {
	my ($self, $matrixString) = @_;
	my (@rows, @cols, @matrix);
	
	# strip containing []
	$matrixString =~ s/^\[(.*)\]$/$1/;
	chomp($matrixString);
	
	# split into rows and each field in row
	my $nRow = 0;
	@rows= split /],/, $matrixString;
	foreach my $row (@rows) {
		my $nCol = 0;
		@cols = split /,/, $row;
		foreach my $item (@cols) {
			# strip whitespace and brackets
			$item =~ s/\[|\]$//;
			$item =~ s/\s//;
			$matrix[$nRow][$nCol] = $item;
			$nCol++;
		}
		$nRow++;
	}
	
	# iterate array, form into FangchengMatrix object
	my $nRows = scalar(@rows);
	my $nCols = scalar(@cols);
	
	if ($nCols != ($nRows + 1)) {
		croak "Malformed matrix: $matrixString";
	}
	
	my $FCMatrix = Fangcheng::Matrix->new( $nRows );
	
	# technically, we're doing pretty much the same thing as FangchengMatrix
	# but if the underlying data structure ever changes, it's better to use
	# the method calls to set the values than just assume it will keep
	# using arrays to structure everything
	for (my $row = 0; $row < $nRows; $row++) {
		for (my $col = 0; $col < $nCols; $col++) {
			$FCMatrix->set($row + 1, $col + 1, $matrix[$row][$col]);	# matrix objects start at 1,1
		}
	}
	
	return($FCMatrix);
}

# retrieve a saved matrix by a named key
sub get {
	my ($self, $key) = @_ or croak "Needs matrix key";

	my $matrices = $self->matrices();
	if ( defined $matrices->{$key} ) {
		return( $matrices->{$key} );
	}
	
	return( undef );
}

# save matrix with a named hash key
sub save {
	my ($self, $key, $matrix) = @_ or croak "Needs key and matrix";
	my $matrices = $self->matrices();
	$matrices->{$key} = $matrix;
	$self->matrices( $matrices );
}

# return a list of all matrix ids
sub list {
	my ($self) = shift;
	my @idList;
	my @keys = keys %{ $self->matrices };
	if ($keys[0] !~ /^\d+$/) {
		return(\@keys);		# we only sort numeric keys
	}
	foreach my $key ( sort { $a <=> $b } @keys ) {
		push @idList, $key;
	}
	return(\@idList);
}

